#include <kuai/misc/error.hpp>
#include <kuai/mol/inchi.hpp>
#include "inchi_api.h"


namespace kuai {

	static void inchi(std::vector<tagInchiAtom>& atoms, Char szOptions[], String result[])
	{
		tagINCHI_Input input = {atoms.empty()?NULL:&atoms[0], NULL, szOptions, atoms.size(), 0};
		tagINCHI_Output output;
		int retcode = GetStdINCHI(&input, &output);

		if (retcode == inchi_Ret_OKAY || inchi_Ret_WARNING) {
			result[0] = output.szAuxInfo?output.szInChI:"";
			result[1] = output.szAuxInfo?output.szAuxInfo:"";
			result[2] = output.szMessage?output.szMessage:"";
			// result[3] = output.szLog?output.szLog:"";
		}
		
		FreeStdINCHI(&output);

		switch (retcode) {
		case inchi_Ret_OKAY:	// =  0, Success; no errors or warnings
		case inchi_Ret_WARNING:	// =  1, Success; warning(s) issued
			return;

		case inchi_Ret_EOF:		// = -1, no structural data has been provided
			result[0] = "InChI=1S//";
			result[1] = "";
			result[2] = "No structural data has been provided";
			// result[3] = "";
			return;

		case inchi_Ret_FATAL:	// = 3, Severe error: no InChI has been created (typically, memory allocation failure)
			throw std::bad_alloc("memory allocation failure for InChI");

		case inchi_Ret_BUSY:	// = 5  Previuos call to InChI has not returned yet
			throw std::runtime_error("InChI is Busy");

		case inchi_Ret_ERROR:	// =  2, Error: no InChI has been created
			throw std::runtime_error("no InChI has been created");

		case inchi_Ret_SKIP:	// = -2, not used in InChI library
		case inchi_Ret_UNKNOWN: // = 4, Unknown program error
		default:
			throw error(SZ_UNKNOWN_ERROR);
		}
	}

	/*
	void inchi(const Molecule& mol, Char szOptions[], String result[]) {
		Index nAtoms = mol.countAtoms();
		std::vector<tagInchiAtom> atoms(nAtoms);
		
		if (!atoms.empty()) {
			memset(&atoms[0], 0, sizeof(tagInchiAtom)*atoms.size());
		}
		
		for (Index i = 0; i < nAtoms; ++i) {
			const Atom& atomI = mol.atoms[i];
			atoms[i].num_iso_H[0] = -1;
			atoms[i].num_bonds = mol.degree(atomI);
			atoms[i].charge = S_CHAR(atomI.charge);
			strcpy(atoms[i].elname, atomI.symbol().c_str());
			for (int j = 0; j < atoms[i].num_bonds; ++j) { 
				const Atom& atomJ = mol.getNeighborAtom(atomI, j);
				atoms[i].neighbor[j] = mol.indexOf(atomJ);
					
				const Bond& bondJ = mol.getNeighborBond(atomI, j);
				switch (bondJ.order) { 
				case Bond::SINGLE_BOND:
					atoms[i].bond_type[j] = INCHI_BOND_TYPE_SINGLE;
					break;

				case Bond::DOUBLE_BOND:
					atoms[i].bond_type[j] = INCHI_BOND_TYPE_DOUBLE;
					break;

				case Bond::TRIPLE_BOND:
					atoms[i].bond_type[j] = INCHI_BOND_TYPE_TRIPLE;
					break;

				case Bond::PARTIAL_BOND:
					atoms[i].bond_type[j] = INCHI_BOND_TYPE_ALTERN;
					break;

				default:
					atoms[i].bond_type[j] = INCHI_BOND_TYPE_NONE;
					break;
				}
			}
		}

		inchi(atoms, szOptions, result);
	}


	void inchi(const SubMolecule<Molecule>& mol, Char szOptions[], String result[]) {
		Index nAtoms = mol.countAtoms();

		std::vector<tagInchiAtom> atoms(nAtoms);
		memset(&atoms[0], 0, sizeof(tagInchiAtom)*atoms.size());
		for (Index i = 0; i < nAtoms; ++i) {
			const Atom& atomI = mol.getAtom(i);
			atoms[i].num_iso_H[0] = -1;
			atoms[i].num_bonds = mol.degree(atomI);
			atoms[i].charge = S_CHAR(atomI.charge);
			strcpy(atoms[i].elname, atomI.symbol().c_str());
			for (int j = 0; j < atoms[i].num_bonds; ++j) { 
				const Atom& atomJ = mol.getNeighborAtom(atomI, j);
				atoms[i].neighbor[j] = mol.indexOf(atomJ);
					
				const Bond& bondJ = mol.getNeighborBond(atomI, j);
				switch (bondJ.order) { 
				case Bond::SINGLE_BOND:
					atoms[i].bond_type[j] = INCHI_BOND_TYPE_SINGLE;
					break;

				case Bond::DOUBLE_BOND:
					atoms[i].bond_type[j] = INCHI_BOND_TYPE_DOUBLE;
					break;

				case Bond::TRIPLE_BOND:
					atoms[i].bond_type[j] = INCHI_BOND_TYPE_TRIPLE;
					break;

				case Bond::PARTIAL_BOND:
					atoms[i].bond_type[j] = INCHI_BOND_TYPE_ALTERN;
					break;

				default:
					atoms[i].bond_type[j] = INCHI_BOND_TYPE_NONE;
					break;
				}
			}
		}

		inchi(atoms, szOptions, result);
	}
	*/

	void inchi(const PhaseSpace& space, Char szOptions[], String result[]) {
		PhaseSpace::const_iterator it  = space.find(MOL_INFO_CT);
		std::vector<tagInchiAtom> atoms;
		if (it != space.end()) {
			const ConnectTable& ct = static_cast<const PropertyConnectTable&>(*(it->second));
			atoms.resize(ct.countAtoms());

			PhaseSpace::const_iterator itBondOrder = space.find(MOL_INFO_BOND_ORDER);
			const Index* pOrder = NULL;
			if (itBondOrder != space.end()) {
				pOrder = static_cast<const PropertyIndex&>(*(itBondOrder->second)).c_ptr();
			}

			PhaseSpace::const_iterator itBondStereo = space.find(MOL_INFO_BOND_STEREO);
			const Index* pStereo = NULL;
			if (itBondStereo != space.end()) {
				pStereo = static_cast<const PropertyIndex&>(*(itBondStereo->second)).c_ptr();
			}

			PhaseSpace::const_iterator itCoord = space.find(MOL_INFO_ATOM_COORD_3D);
			const XYZ* pCoord = NULL;
			if (itCoord != space.end()) {
				pCoord = static_cast<const PropertyXYZ&>(*(itCoord->second)).c_ptr();
			}

			if (!atoms.empty()) {
				memset(&atoms[0], 0, sizeof(tagInchiAtom)*atoms.size());
				for (size_t i = 0; i < atoms.size(); ++i) {
					const Atom& atomI = ct.getAtom(i);
					atoms[i].num_iso_H[0] = -1;
					atoms[i].num_bonds = AT_NUM(ct.degree(atomI));
					strcpy(atoms[i].elname, Element::getElement(atomI.type).symbol.c_str());
					if (pCoord != NULL) {
						atoms[i].x = pCoord[i].x;
						atoms[i].y = pCoord[i].y;
						atoms[i].z = pCoord[i].z;
					} 
					for (int j = 0; j < atoms[i].num_bonds; ++j) { 
						const Atom& atomJ = ct.getNeighborAtom(atomI, j);
						atoms[i].neighbor[j] = AT_NUM(ct.indexOf(atomJ));
							
						const Bond& bondJ = ct.getNeighborBond(atomI, j);
						Index bondID = ct.indexOf(bondJ);
						Index order;
						if (pOrder != NULL) {
							order = pOrder[bondID];
						}
						else {
							order = bondJ.order;
						}
						switch (order) { 
						case Bond::SINGLE_BOND:
							atoms[i].bond_type[j] = INCHI_BOND_TYPE_SINGLE;
							break;

						case Bond::DOUBLE_BOND:
							atoms[i].bond_type[j] = INCHI_BOND_TYPE_DOUBLE;
							break;

						case Bond::TRIPLE_BOND:
							atoms[i].bond_type[j] = INCHI_BOND_TYPE_TRIPLE;
							break;

						case Bond::PARTIAL_BOND:
							atoms[i].bond_type[j] = INCHI_BOND_TYPE_ALTERN;
							break;

						default:
							atoms[i].bond_type[j] = INCHI_BOND_TYPE_NONE;
							break;
						}
						if (pStereo) {
							if (pStereo[bondID] == 3) {
								atoms[i].bond_stereo[j] = INCHI_BOND_STEREO_DOUBLE_EITHER;
							}
							else {
								if (&ct.getAtom(bondJ, 0) == &atomI) {
									atoms[i].bond_stereo[j] = S_CHAR(pStereo[bondID]);
								}
								else {
									assert (&ct.getAtom(bondJ, 1) == &atomI);
									atoms[i].bond_stereo[j] = -S_CHAR(pStereo[bondID]);
								}
							}
						}
					} // for (int j = 0; j < atoms[i].num_bonds; ++j)
				} // for (size_t i = 0; i < atoms.size(); ++i) {

				it  = space.find(MOL_INFO_ATOM_CHARGE);
				if (it != space.end()) {
					const Integer* pCharge = static_cast<const PropertyInteger&>(*(it->second)).c_ptr();
					for (size_t i = 0; i < atoms.size(); ++i) {
						atoms[i].charge = S_CHAR(pCharge[i]);
					}
				}
				it  = space.find(MOL_INFO_ATOM_ISO);
				if (it != space.end()) {
					const Index* pIso = static_cast<const PropertyIndex&>(*(it->second)).c_ptr();
					for (size_t i = 0; i < atoms.size(); ++i) {
						atoms[i].isotopic_mass = AT_NUM(pIso[i]);
					}
				}
				it  = space.find(MOL_INFO_ATOM_RADICAL);
				if (it != space.end()) {
					const Index* pRad = static_cast<const PropertyIndex&>(*(it->second)).c_ptr();
					for (size_t i = 0; i < atoms.size(); ++i) {
						atoms[i].radical = S_CHAR(pRad[i]);
					}
				}
				it = space.find(MOL_INFO_ATOM_IMPLICIT_H);
				if (it != space.end()) {
					const Index* pH = static_cast<const PropertyIndex&>(*(it->second)).c_ptr();
					for (size_t i = 0; i < atoms.size(); ++i) {
						atoms[i].num_iso_H[0] = S_CHAR(pH[i]);
					}
				}

				
			} // if (!atoms.empty()) {
		} // if (it != space.end()) {
		inchi(atoms, szOptions, result);
	}

	void inchi(const ConnectTable& ct, Char szOptions[], String result[]) {
		std::vector<tagInchiAtom> atoms;
		atoms.resize(ct.countAtoms());

		if (!atoms.empty()) {
			memset(&atoms[0], 0, sizeof(tagInchiAtom)*atoms.size());
			for (size_t i = 0; i < atoms.size(); ++i) {
				const Atom& atomI = ct.getAtom(i);
				atoms[i].num_iso_H[0] = -1;
				atoms[i].num_bonds = AT_NUM(ct.degree(atomI));
				strcpy(atoms[i].elname, Element::getElement(atomI.type).symbol.c_str());
				for (int j = 0; j < atoms[i].num_bonds; ++j) { 
					const Atom& atomJ = ct.getNeighborAtom(atomI, j);
					atoms[i].neighbor[j] = AT_NUM(ct.indexOf(atomJ));
						
					const Bond& bondJ = ct.getNeighborBond(atomI, j);
					Index bondID = ct.indexOf(bondJ);
					switch (bondJ.order) { 
					case Bond::SINGLE_BOND:
						atoms[i].bond_type[j] = INCHI_BOND_TYPE_SINGLE;
						break;

					case Bond::DOUBLE_BOND:
						atoms[i].bond_type[j] = INCHI_BOND_TYPE_DOUBLE;
						break;

					case Bond::TRIPLE_BOND:
						atoms[i].bond_type[j] = INCHI_BOND_TYPE_TRIPLE;
						break;

					case Bond::PARTIAL_BOND:
						atoms[i].bond_type[j] = INCHI_BOND_TYPE_ALTERN;
						break;

					default:
						atoms[i].bond_type[j] = INCHI_BOND_TYPE_NONE;
						break;
					}
				} // for (int j = 0; j < atoms[i].num_bonds; ++j)
			} // for (size_t i = 0; i < atoms.size(); ++i) {
		}
		inchi(atoms, szOptions, result);
	}
	void inchi(const SubMolecule<ConnectTable>& ct, Char szOptions[], String result[]) {
		std::vector<tagInchiAtom> atoms;
		atoms.resize(ct.countAtoms());

		if (!atoms.empty()) {
			memset(&atoms[0], 0, sizeof(tagInchiAtom)*atoms.size());
			for (size_t i = 0; i < atoms.size(); ++i) {
				const Atom& atomI = ct.getAtom(i);
				atoms[i].num_iso_H[0] = -1;
				atoms[i].num_bonds = AT_NUM(ct.degree(atomI));
				strcpy(atoms[i].elname, Element::getElement(ct.getAtom(i).type).symbol.c_str());
				for (int j = 0; j < atoms[i].num_bonds; ++j) { 
					const Atom& atomJ = ct.getNeighborAtom(atomI, j);
					atoms[i].neighbor[j] = AT_NUM(ct.indexOf(atomJ));
						
					const Bond& bondJ = ct.getNeighborBond(atomI, j);
					Index bondID = ct.indexOf(bondJ);
					switch (bondJ.order) { 
					case Bond::SINGLE_BOND:
						atoms[i].bond_type[j] = INCHI_BOND_TYPE_SINGLE;
						break;

					case Bond::DOUBLE_BOND:
						atoms[i].bond_type[j] = INCHI_BOND_TYPE_DOUBLE;
						break;

					case Bond::TRIPLE_BOND:
						atoms[i].bond_type[j] = INCHI_BOND_TYPE_TRIPLE;
						break;

					case Bond::PARTIAL_BOND:
						atoms[i].bond_type[j] = INCHI_BOND_TYPE_ALTERN;
						break;

					default:
						atoms[i].bond_type[j] = INCHI_BOND_TYPE_NONE;
						break;
					}
				} // for (int j = 0; j < atoms[i].num_bonds; ++j)
			} // for (size_t i = 0; i < atoms.size(); ++i) {
		}
		inchi(atoms, szOptions, result);
	};
	

	String inchikey(const Char inchi[]) {
		Char result[32];

		switch (GetStdINCHIKeyFromStdINCHI(inchi, result)) {
		case INCHIKEY_OK:
			return result;

		case INCHIKEY_EMPTY_INPUT:
		case INCHIKEY_NOT_INCHI_INPUT:
		case INCHIKEY_NOT_ENOUGH_MEMORY:
		case INCHIKEY_ERROR_IN_FLAG_CHAR:
		case INCHIKEY_INCHI_REVERSAL_FAIL:
		case INCHIKEY_INCHI_REVERSED_NOT_THE_SAME:
		case INCHIKEY_INVALID_STD_INCHI:
		case INCHIKEY_UNKNOWN_ERROR:
		default:
			throw error(SZ_UNKNOWN_ERROR);
			break;
		};
	}

	void inchikey(const PhaseSpace& space, Char szOptions[], String result[]) {
		inchi(space, szOptions, result);
		result[3] = inchikey(result[0].c_str());
	}
}
